package role

import (
	"fmt"

	"gitee.com/chenhonghua/ginorigin/database"
	"github.com/patrickmn/go-cache"
	"gorm.io/gorm"
)

var SECURITY_ROLE_CACHE *cache.Cache = cache.New(0, 0)

type RolePolicy struct {
	RoleId    uint64   `gorm:"column:rid;comment:角色ID"`
	PolicySub string   `gorm:"column:policy_sub;comment:权限主体标记"`
	Policies  []Policy `json:"policies,omitempty" gorm:"-"` // gorm:"foreignKey:id;references:policy_id;comment:权限"`
}

func (RolePolicy) TableName() string {
	return "security_role_policy"
}

type Role struct {
	database.BaseModel
	Name         string       `json:"name" gorm:"column:name;type:varchar(256);comment:角色名称"`
	Description  string       `json:"description" gorm:"column:description;type:varchar(1024);comment:描述"`
	RolePolicies []RolePolicy `json:"rolePolicies,omitempty" gorm:"foreignKey:rid;references:id;comment:权限"`
	ParentId     uint64       `json:"parentId,omitempty" gorm:"column:parent_id;comment:父级角色ID"`
	Parent       *Role        `json:"parent,omitempty" gorm:"foreignKey:id;references:parent_id;comment:父级角色"`
	Children     []Role       `json:"children,omitempty" gorm:"foreignKey:parent_id;references:id;comment:子级角色"`
}

func (Role) TableName() string {
	return "security_role"
}

type UserRole struct {
	Uid    uint64 `json:"uid" gorm:"column:uid;comment:用户ID"`
	RoleId uint64 `json:"roleId" gorm:"column:rid;comment:角色ID"`
	Role   *Role  `json:"role,omitempty" gorm:"foreignKey:id;references:rid;comment:角色"`
}

func (UserRole) TableName() string {
	return "security_user_role"
}

func (r Role) Match(dest *Policy) bool {
	if dest == nil {
		return true
	}
	if len(r.RolePolicies) > 0 {
		for _, rp := range r.RolePolicies {
			for _, p := range rp.Policies {
				if p.Match(dest) {
					return true
				}
			}
		}
	}
	if len(r.Children) > 0 {
		for _, child := range r.Children {
			if child.Match(dest) {
				return true
			}
		}
	}
	return false
}

// 增
func (r Role) Add() (role Role) {
	database.Transaction(func(tx *gorm.DB) error {
		con := database.GetConnection().Model(r).Create(&r) // 增加操作
		con.Where("id=?", r.ID).First(&role)                // 根据id查询insert结果
		return nil
	})
	return role
}

// 改
func (r Role) Update() error {
	// 注意：更新操作如果携带了无ID的对象属性（如chapter，author等），则会创建这些对象
	return database.GetConnection().Model(r).Where("id = ?", r.ID).Updates(&r).Error
}

// 删
func (r Role) Delete() error {
	tmp := Role{BaseModel: database.BaseModel{ID: r.ID}}
	return database.GetConnection().Model(r).Where("id = ?", r.ID).Delete(&tmp).Error
}

// 查。根据id或parentId查询
func findRole(idName string, id uint64) ([]Role, error) {
	role := Role{}
	result := []Role{}
	query := database.GetConnection().Model(&role).Where(idName+" = ? ", id)
	query = query.Preload("RolePolicies") // 关联权限信息
	err := query.Find(&result).Error
	for err != nil {
		return nil, err
	}
	for i1, r := range result {
		for i2, rp := range r.RolePolicies {
			rp.Policies = policyDictionary[rp.PolicySub]
			r.RolePolicies[i2] = rp
		}
		result[i1] = r
	}
	return result, nil
}

// 根据id，递归查询角色树
func recursiveFindRole(id uint64) (r *Role) {
	cacheKey := fmt.Sprintf("%s::%d", Role{}.TableName(), id)
	if v, b := SECURITY_ROLE_CACHE.Get(cacheKey); b {
		return v.(*Role)
	}
	roles, e := findRole("id", id)
	if e != nil || len(roles) == 0 {
		return nil
	}
	r = &roles[0]
	r.Children, e = findRole("parent_id", r.ID)
	if e != nil || len(roles) == 0 {
		SECURITY_ROLE_CACHE.SetDefault(cacheKey, r)
		return r
	}
	for i, r := range r.Children { // 递归查询角色
		tmp := recursiveFindRole(r.ID)
		if nil != tmp && len(tmp.Children) > 0 {
			r.Children[i].Children = tmp.Children
		}
	}
	SECURITY_ROLE_CACHE.SetDefault(cacheKey, r)
	return r
}

// 查询用户完整角色权限
func FindUserRoleByUid(uid uint64) (urs []UserRole, err error) {
	urs, err = findUserRoleByUid(uid)
	if err != nil {
		return nil, err // 数据库中查不到
	}
	for i, ur := range urs {
		r := recursiveFindRole(ur.RoleId)
		if r != nil {
			ur.Role = r
			urs[i] = ur
		}
	}
	return urs, err
}

// 查询用户直属角色（关联）
// 用户数据量不确定，所以，此处，区分为本地化、中心化两种存储模式
func findUserRoleByUid(uid uint64) ([]UserRole, error) {
	cacheKey := fmt.Sprintf("%s::%d", UserRole{}.TableName(), uid)
	if tmp, b := SECURITY_ROLE_CACHE.Get(cacheKey); b {
		return tmp.([]UserRole), nil
	}
	var urs []UserRole
	if e := database.GetConnection().Model(UserRole{}).Where("uid = ?", uid).Find(&urs).Error; e != nil {
		return nil, e
	}
	SECURITY_ROLE_CACHE.SetDefault(cacheKey, urs)
	return urs, nil
}
